package cn.bonoon.kernel.io.devices;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContext;

import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import cn.bonoon.kernel.io.BFile;
import cn.bonoon.kernel.io.FileInfo;
import cn.bonoon.kernel.io.FileSize;
import cn.bonoon.kernel.io.FileStoreHandler;
import cn.bonoon.kernel.io.StorageFile;

/**
 * 本地的存储设备，表示这个位置是通过路径指定的空间，不在服务器本地可访问的目录内
 * 包括临时目录，也使用这个类来定义
 * 
 * @author jackson
 *
 */
public class LocalStorageDevice implements StorageDevice, StorageDeviceInitizlier{
	
	private final static long DEFAULT_STORAGE_MIN_LENGTH = 100 * FileSize.M;
	
	/** 定义的路径，这个路径是未经过处理的原路径，可能是相对路径等。
	 * 必须是本地可以访问的，如本地目录、挂载目录等 */
	private final String sourcePath;//upload/pic2
	
	/** 使用本地的设备的，一般情况下，小于100M就不再存储文件了 */
	private final FileSize minFreeSpace;
	
	private ServletContext servletContext;
	
	private File root;
	
	private final static String PRIFIX = "file:";
	
	private int ordinal;
	
	public LocalStorageDevice(String sourcePath, long size){
		Assert.hasText(sourcePath, "必须定义存储设备的存储路径！");
		this.sourcePath = sourcePath;
		this.minFreeSpace = FileSize.valueOf(size > 0 ? size : DEFAULT_STORAGE_MIN_LENGTH);
	}
	
	public LocalStorageDevice(String sourcePath){
		this(sourcePath, DEFAULT_STORAGE_MIN_LENGTH);
	}
	
	public StorageDevice init(ServletContext servletContext){
		this.servletContext = servletContext;
		
		return this;
	}
	
	private File internalRoot(){
		
		if(sourcePath.startsWith(PRIFIX)){
			root = new File(sourcePath.substring(PRIFIX.length()));
		}else{
			root = new File(servletContext.getRealPath(sourcePath));
		}
		
		if(!root.isDirectory()){
			root.mkdirs();
		}
		return root;
	}
	
	public File open(BFile file){
		String filepath = file.getFilepath();
		if(filepath.startsWith(sourcePath)){
			/*
			 * 路径里已经包括指定的根目录(rootDirectory)，所以
			 * 不需要再进行处理，表示路径为直接指定
			 */
			return new File(servletContext.getRealPath(filepath));
		}
		
		return new File(internalRoot(), filepath);
	}

	protected FileStoreHandler storageHandler = new FileStoreHandler(){

		@Override
		public FileInfo stored(FileInfo fileInfo) throws Exception {
			fileInfo.getSourceFile().transferTo(fileInfo.getFile());
			return fileInfo;
		}
		
	};
	
	protected FileStoreHandler findStoreHandler(StorageFile file){
		FileStoreHandler storageHandler = file.getStorageHandler();
		if(null == storageHandler) storageHandler = this.storageHandler;
		return storageHandler;
	}
	public FileInfo[] save(StorageFile file, List<MultipartFile> mFiles, int limit) throws Exception{

		File folder = open(file);
		if(!folder.isDirectory()) folder.mkdirs();
		
		//处理的结果，表示已经处理了多少个上传文件
		FileStoreHandler storeHandler = findStoreHandler(file);
		List<FileInfo> fis = new ArrayList<>();
		
		for(MultipartFile mf : mFiles){
			
			FileInfo fileInfo = null;
			for(int i = 0; ; i++){
				/* 具体需要被存储的文件 */
				File targetfile = new File(folder, file.resolveFilename(mf, i));
				if(!targetfile.exists()) {
					fileInfo = new FileInfo(mf, targetfile, file);
					break;
				}
			}
			
			fileInfo = storeHandler.stored(fileInfo);
			
			if(null != fileInfo){
				//处理器"storeHandler"可能返回null值，表示这个文件存储不成功
				fis.add(fileInfo);
				if(limit > 0 && fis.size() >= limit) break;
			}
		}
		return fis.toArray(new FileInfo[fis.size()]);
	}
	
	/** 这个设备是否有足够使用的空间 */
	public boolean checkFreeSpace(long needSize){
		//if(!root.isDirectory()) root.mkdirs();//如果目录不存在是获取不到空间的，项目启动的时候会自己创建目录，如果启动之后再删除了目录就不会再创建了

		long free = internalRoot().getUsableSpace();//.getFreeSpace();
		return (free > minFreeSpace.size()) && (free > needSize);
	}
	
	public String getSourcePath() {
		return sourcePath;
	}

	@Override
	public int compareTo(StorageDevice o) {
		return ordinal - o.ordinal();
	}

	@Override
	public int ordinal() {
		return ordinal;
	}
	
	public LocalStorageDevice ordinal(int ordinal){
		setOrdinal(ordinal);
		
		return this;
	}
	
	public void setOrdinal(int ordinal) {
		this.ordinal = ordinal;
	}
}
